#pragma once

#include "DCoordinates.h"
#include <iostream>
#include "Matrices.h"
#include "TriangulatedMeshes.h"
#include <vector>

namespace cagd
{
    class SurfaceInformation
    {
    public:
        // point of surface
        DCoordinate point;

        // first ordered partial derivates of the surface
        DCoordinate diff1u, diff1v;

        // second ordered partial derivates of the surface
        DCoordinate diff2uu, diff2uv, diff2vv;

        // third ordered partial derivates of the surface
        DCoordinate diff3uuu, diff3uuv, diff3uvv, diff3vvv;

        // default constructor
        SurfaceInformation() {}

        // initializes the information above to zero-vector
        GLvoid LoadNullVectors()
        {
            point.x = point.y = point.z = 0.0;

            diff1u.x = diff1u.y = diff1u.z = 0.0;
            diff1v.x = diff1v.y = diff1v.z = 0.0;

            diff2uu.x = diff2uu.y = diff2uu.z = 0.0;
            diff2uv.x = diff2uv.y = diff2uv.z = 0.0;
            diff2vv.x = diff2vv.y = diff2vv.z = 0.0;

            diff3uuu.x = diff3uuu.y = diff3uuu.z = 0.0;
            diff3uuv.x = diff3uuv.y = diff3uuv.z = 0.0;
            diff3uvv.x = diff3uvv.y = diff3uvv.z = 0.0;
            diff3vvv.x = diff3vvv.y = diff3vvv.z = 0.0;
        }
    };


        class AbstractTensorProductSurface
    {
    protected:
        GLboolean           _is_closed;
        GLuint              _vbo_control_net;
        GLdouble            _u_min, _u_max;
        GLdouble            _v_min, _v_max;
        Matrix<DCoordinate> _control_net;

    public:
        // special constructor
        AbstractTensorProductSurface(
                GLdouble u_min, GLdouble u_max,
                GLdouble v_min, GLdouble v_max,
                GLuint row_count = 4, GLuint column_count = 4,
                GLboolean is_closed = false);

        // copy constructor
        AbstractTensorProductSurface(const AbstractTensorProductSurface& surface);

        // assignment operator
        AbstractTensorProductSurface& operator =(const AbstractTensorProductSurface& surface);

        // set/get the definition domain of the surface
        GLvoid SetUInterval(GLdouble u_min, GLdouble u_max);
        GLvoid SetVInterval(GLdouble v_min, GLdouble v_max);

        GLvoid GetUInterval(GLdouble& u_min, GLdouble& u_max) const;
        GLvoid GetVInterval(GLdouble& v_min, GLdouble& v_max) const;

        // set/get a selected control point
        GLboolean SetControlPoint(GLuint row, GLuint column, GLdouble x, GLdouble y, GLdouble z);
        GLboolean SetControlPoint(GLuint row, GLuint column, const DCoordinate& point);

        GLboolean GetControlPoint(GLuint row, GLuint column, GLdouble& x, GLdouble& y, GLdouble& z) const;
        GLboolean GetControlPoint(GLuint row, GLuint column, DCoordinate& point) const;

        // blending function values in u- and v-direction
        virtual GLboolean UBlendingFunctionValues(GLdouble u_knot, RowMatrix<GLdouble>& blending_values) const = 0;
        virtual GLboolean VBlendingFunctionValues(GLdouble v_knot, RowMatrix<GLdouble>& blending_values) const = 0;

        // calculates the point and higher (mixed) ordered partial derivatives of the tensor product surface
        // s(u, v) = \sum_{r=0}^n \sum{c = 0}^m p_{ij} F_i(u) G_j(v), (u, v) \in [u_{\min}, u_{\max}] \times [v_{\min}, v_{\max}]
        virtual GLboolean CalculateAllInformation(GLdouble u, GLdouble v, SurfaceInformation& pd) const = 0;

        // generates a triangular mesh that approximates the shape of the surface above
        GLboolean GenerateMesh(GLuint u_div_point_count, GLuint v_div_point_count, TriangulatedMesh& mesh) const;

        // ensures interpolation, i.e. s(u_i, v_j) = d_{ij}
        GLboolean UpdateControlPointsForInterpolation(
                const RowMatrix<GLdouble>& u_knot_vector,
                const ColumnMatrix<GLdouble>& v_knot_vector,
                Matrix<DCoordinate>& data_points);

        // these methods handle the VBO of the control net
        GLvoid DeleteVBOOfControlNet();
        GLboolean RenderControlNet(GLenum render_mode = GL_LINE_STRIP) const;
        GLboolean UpdateVBOOfControlNet(GLenum usage_type = GL_STATIC_DRAW);

        // destructor
        virtual ~AbstractTensorProductSurface();
    };
}
